/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.editor.model;

import java.io.IOException;

import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.html.HTML;

/**
 *
 * @author
 * Walter
 * Lütgenau
 */
public class TableModel extends EditorModel {

    public static final int COLUMN_WIDTH_TYPE_FIXED = 1;
    public static final int COLUMN_WIDTH_TYPE_FIXED_PERCENT = 2;
    public static final int COLUMN_WIDTH_TYPE_VARIABLE = 3;
    public static final String LABEL_STYLE = "label";

    public TableModel(DocumentContext context) {
        super(context);
    }

    public boolean isTable(int pos) {
        Element table = getAncestor(pos, HTML.Tag.TABLE);
        return table != null;
    }

    public boolean isEndOfTable(int pos) {
        Element row = getAncestor(pos, HTML.Tag.TR);
        Element table = row.getParentElement();
        if (table.getElementCount() > getChildIndex(row) + 1)
            return false;
        Element cell = row.getElement(row.getElementCount() - 1);

        return pos == cell.getEndOffset() - 1;
    }

    public boolean isLastCellOfRow(int pos) {
        Element cell = getAncestor(pos, HTML.Tag.TD);
        if (cell == null)
            cell = getAncestor(pos, HTML.Tag.TH);
        int childIndex = getChildIndex(cell);
        return childIndex == cell.getParentElement().getElementCount() - 1;
    }

    public boolean isFirstRow(int pos) {
        Element row = getAncestor(pos, HTML.Tag.TR);
        return getChildIndex(row) == 0;
    }

    public boolean isLastRow(int pos) {
        Element row = getAncestor(pos, HTML.Tag.TR);
        Element table = row.getParentElement();
        return getChildIndex(row) == table.getElementCount() - 1;
    }

    public boolean isSameColumn(int pos1, int pos2) {
        Element cell1 = getAncestor(pos1, HTML.Tag.TD);
        Element cell2 = getAncestor(pos2, HTML.Tag.TD);

        if (cell1 == null || cell2 == null)
            return false;

        return getChildIndex(cell1) == getChildIndex(cell2);
    }

    public int getStartOfTable(int pos) {
        Element table = getAncestor(pos, HTML.Tag.TABLE);
        return table.getStartOffset();
    }

    public int getEndOfTable(int pos) {
        Element table = getAncestor(pos, HTML.Tag.TABLE);
        return table.getEndOffset();
    }

    public int getStartOfCell(int pos) {
        Element paragraph = getDocument().getParagraphElement(pos);
        Element cell = paragraph.getParentElement();
        return cell.getStartOffset();
    }

    public Range getCurrentCellRange(int pos) {
        Element paragraph = getDocument().getParagraphElement(pos);
        Element cell = paragraph.getParentElement();
        return new Range(cell.getStartOffset(), cell.getEndOffset());
    }

    public int getStartOfNextCell(int pos) {
        Element cell = getAncestor(pos, HTML.Tag.TD);
        if (cell == null)
            cell = getAncestor(pos, HTML.Tag.TH);
        int childIndex = getChildIndex(cell);

        cell = cell.getParentElement().getElement(childIndex + 1);
        return cell.getStartOffset();
    }

    public int getTableWidthType(int pos) {
        Element paragraph = getDocument().getParagraphElement(pos);
        Element cell = paragraph.getParentElement();
        Element row = cell.getParentElement();
        Element table = row.getParentElement();

        String width = (String) table.getAttributes().getAttribute(HTML.Attribute.WIDTH);
        if (width == null)
            return COLUMN_WIDTH_TYPE_VARIABLE;
        else if (width.endsWith("%"))
            return COLUMN_WIDTH_TYPE_FIXED_PERCENT;
        else
            return COLUMN_WIDTH_TYPE_FIXED;
    }

    public String getTableWidthValue(int pos) {
        Element paragraph = getDocument().getParagraphElement(pos);
        Element cell = paragraph.getParentElement();
        Element row = cell.getParentElement();
        Element table = row.getParentElement();

        return (String) table.getAttributes().getAttribute(HTML.Attribute.WIDTH);

    }

    public int getColumnWidth(int pos, int totalWidth) {
        Element paragraph = getDocument().getParagraphElement(pos);
        Element cell = paragraph.getParentElement();
        return getOuterColumnWidth(cell, totalWidth);
    }

    protected int getOuterColumnWidth(Element cell, int totalWidth) {
        if (cell.getAttributes().getAttribute(HTML.Attribute.WIDTH) != null) {
            String width = (String) cell.getAttributes().getAttribute(HTML.Attribute.WIDTH);
            if (width.endsWith("%")) {
                int perCent = Integer.parseInt(width.substring(0, width.indexOf("%")));
                return totalWidth * perCent / 100;
            } else
                return Integer.parseInt(width);
        }

        int tableWidth = totalWidth;
        Element outerCell = getAncestor(cell.getParentElement(), HTML.Tag.TD);
        if (outerCell != null)
            tableWidth = getOuterColumnWidth(outerCell, totalWidth);
        Element row = getAncestor(cell, HTML.Tag.TR);
        if (row == null)
            row = getAncestor(cell, HTML.Tag.TH);

        return tableWidth / row.getElementCount();
    }

    protected int getOuterColumnWidth(int pos, int totalWidth) {
        if (!isTable(pos))
            return totalWidth;

        Element cell = getAncestor(pos, HTML.Tag.TD);
        return getOuterColumnWidth(cell, totalWidth);
    }

    protected String encodeWidth(int columnCount, int columnWidthType, int tableWidth, int totalWidth) {
        String columnWidth = null;

        switch (columnWidthType) {
            case COLUMN_WIDTH_TYPE_FIXED:
                columnWidth = " width=\"" + tableWidth / columnCount + "\"";
                break;

            case COLUMN_WIDTH_TYPE_FIXED_PERCENT:
                columnWidth = " width=\"" + (tableWidth * 100 / totalWidth) / columnCount + "%\"";
                break;

            case COLUMN_WIDTH_TYPE_VARIABLE:
                columnWidth = "";
        }

        return columnWidth;
    }

    protected String createRow(int rowCount, int columnCount, int columnWidthType, int tableWidth, int totalWidth, boolean headerLabels, boolean columnLabels) {
        StringBuffer buf = new StringBuffer("<tr valign=\"top\">");

        for (int i = 0; i < columnCount; i++) {
            String style = "";
            if ((headerLabels && rowCount == 0) || columnLabels && ((i % 2) == 0))
                style = " class='" + LABEL_STYLE + "'";

            buf.append("<td" + encodeWidth(columnCount, columnWidthType, tableWidth, totalWidth) + style + ">" + i + "</" + "td>");
        }

        buf.append("</tr>");

        return buf.toString();
    }

    public Range insertTable(int pos, int rowCount, int columnCount, int columnWidthType, int totalWidth, boolean headerLabels, boolean columnLabels) throws BadLocationException, IOException {
        int tableWidth = totalWidth;
        if (isTable(pos))
            tableWidth = getOuterColumnWidth(pos, totalWidth);
        StringBuffer buf = new StringBuffer("<table");

        switch (columnWidthType) {
            case COLUMN_WIDTH_TYPE_FIXED:
                buf.append(" width=\"" + tableWidth + "\"");
                break;

            case COLUMN_WIDTH_TYPE_FIXED_PERCENT:
                buf.append(" width=\"" + tableWidth * 100 / totalWidth + "%\"");
                break;
        }
        buf.append(">");

        for (int i = 0; i < rowCount; i++) {
            buf.append(createRow(i, columnCount, columnWidthType, tableWidth, totalWidth, headerLabels, columnLabels));
        }

        buf.append("</table>");

        Element e = getDocument().getParagraphElement(pos);
        getDocument().setOuterHTML(e, buf.toString() + getOuterHTML(e));
        e = getDocument().getParagraphElement(pos);

        return new Range(e.getStartOffset(), e.getEndOffset() - 1);
    }

    public Range addRowAfter(int pos) throws BadLocationException, IOException {
        Element row = getAncestor(pos, HTML.Tag.TR);

        StringBuffer buf = new StringBuffer(getOuterHTML(row));

        buf.append(getStartTag(row.getAttributes()));
        for (int i = 0; i < row.getElementCount(); i++) {
            Element cell = row.getElement(i);

            SimpleAttributeSet atts = new SimpleAttributeSet(cell.getAttributes());
            atts.removeAttribute(StyleConstants.NameAttribute);
            atts.addAttribute(StyleConstants.NameAttribute, HTML.Tag.TD.toString());
            buf.append(getStartTag(atts));

            buf.append("" + i);
            buf.append("</td>");
        }
        buf.append("</tr");

        getDocument().setOuterHTML(row, buf.toString());

        Element firstNewCell = getAncestor(pos + 1, HTML.Tag.TD);

        return new Range(firstNewCell.getStartOffset(), firstNewCell.getEndOffset() - 1);
    }

    public Range nextCell(int pos) {
        Element cell = getAncestor(pos, HTML.Tag.TD);
        if (cell == null)
            cell = getAncestor(pos, HTML.Tag.TH);
        Element row = cell.getParentElement();

        if (row.getElementCount() > getChildIndex(cell) + 1) {
            cell = row.getElement(getChildIndex(cell) + 1);
            return new Range(cell.getStartOffset(), cell.getEndOffset() - 1);
        } else {
            Element table = row.getParentElement();
            if (table.getElementCount() > getChildIndex(row) + 1) {
                row = table.getElement(getChildIndex(row) + 1);
                cell = row.getElement(0);
                return new Range(cell.getStartOffset(), cell.getEndOffset() - 1);
            }
        }

        return null;
    }

    public Range previousCell(int pos) {
        Element cell = getAncestor(pos, HTML.Tag.TD);
        if (cell == null)
            cell = getAncestor(pos, HTML.Tag.TH);
        Element row = cell.getParentElement();

        if (getChildIndex(cell) > 0) {
            cell = row.getElement(getChildIndex(cell) - 1);
            return new Range(cell.getStartOffset(), cell.getEndOffset() - 1);
        } else {
            Element table = row.getParentElement();
            if (getChildIndex(row) > 0) {
                row = table.getElement(getChildIndex(row) - 1);
                cell = row.getElement(row.getElementCount() - 1);
                return new Range(cell.getStartOffset(), cell.getEndOffset() - 1);
            }
        }

        return null;
    }

    public void updateColumnWidth(int pos, int startX, int endX, int newEndX) throws BadLocationException, IOException {
        Element cell = getDocument().getParagraphElement(pos).getParentElement();
        int tableWidthType = getTableWidthType(pos);

        String oldWidth = (String) cell.getAttributes().getAttribute(HTML.Attribute.WIDTH);
        if (oldWidth == null)
            return;

        int difference = 0;
        if (oldWidth.endsWith("%")) {
            oldWidth = oldWidth.substring(0, oldWidth.length() - 1);
            int oldW = Integer.parseInt(oldWidth);
            int newW = oldW * (newEndX - startX) / (endX - startX);
            difference = newW - oldW;
        } else {
            int oldW = Integer.parseInt(oldWidth);
            difference = newEndX - endX;
        }

        Element table = getAncestor(cell.getStartOffset(), HTML.Tag.TABLE);

        int cellIndex = getChildIndex(cell);
        boolean tableWidthChangeIsBalanced = false;
        for (int i = 0; i < table.getElementCount(); i++) {
            Element row = table.getElement(i);

            cell = row.getElement(cellIndex);
            SimpleAttributeSet atts = new SimpleAttributeSet(cell.getAttributes());

            oldWidth = (String) atts.getAttribute(HTML.Attribute.WIDTH);
            String newWidth = null;
            if (tableWidthType == TableModel.COLUMN_WIDTH_TYPE_FIXED_PERCENT) {
                int oldW = Integer.parseInt(oldWidth.substring(0, oldWidth.length() - 1));
                newWidth = "" + (oldW + difference) + "%";
            } else {
                int oldW = Integer.parseInt(oldWidth);
                newWidth = "" + (oldW + difference);
            }
            atts.removeAttribute(HTML.Attribute.WIDTH);
            atts.addAttribute(HTML.Attribute.WIDTH, newWidth);

            getDocument().setElementAttributes(cell, atts, true);

            if (row.getElementCount() > cellIndex + 1) {
                cell = row.getElement(cellIndex + 1);
                atts = new SimpleAttributeSet(cell.getAttributes());

                oldWidth = (String) atts.getAttribute(HTML.Attribute.WIDTH);
                newWidth = null;
                if (tableWidthType == TableModel.COLUMN_WIDTH_TYPE_FIXED_PERCENT) {
                    int oldW = Integer.parseInt(oldWidth.substring(0, oldWidth.length() - 1));
                    newWidth = "" + (oldW - difference) + "%";
                } else {
                    int oldW = Integer.parseInt(oldWidth);
                    newWidth = "" + (oldW - difference);
                }
                atts.removeAttribute(HTML.Attribute.WIDTH);
                atts.addAttribute(HTML.Attribute.WIDTH, newWidth);

                getDocument().setElementAttributes(cell, atts, true);

                tableWidthChangeIsBalanced = true;
            }
        }

        if (!tableWidthChangeIsBalanced) { // update table width
            SimpleAttributeSet atts = new SimpleAttributeSet(table.getAttributes());

            oldWidth = (String) atts.getAttribute(HTML.Attribute.WIDTH);
            String newWidth = null;
            if (tableWidthType == TableModel.COLUMN_WIDTH_TYPE_FIXED_PERCENT) {
                int oldW = Integer.parseInt(oldWidth.substring(0, oldWidth.length() - 1));
                newWidth = "" + (oldW + difference) + "%";
            } else {
                int oldW = Integer.parseInt(oldWidth);
                newWidth = "" + (oldW + difference);
            }
            atts.removeAttribute(HTML.Attribute.WIDTH);
            atts.addAttribute(HTML.Attribute.WIDTH, newWidth);

            getDocument().setElementAttributes(table, atts, true);
        }
    }

    public void reset(int pos) throws BadLocationException, IOException {
        Element table = getAncestor(pos, HTML.Tag.TABLE);
        if (table != null)
            getDocument().setOuterHTML(table, getOuterHTML(table));
    }

    public void insertRowBelow(int pos) throws BadLocationException, IOException {
        Element row = getAncestor(pos, HTML.Tag.TR);

        StringBuffer buf = new StringBuffer(getOuterHTML(row));
        buf.append(getStartTag(row.getAttributes()));

        for (int i = 0; i < row.getElementCount(); i++) {
            Element cell = row.getElement(i);

            buf.append(getStartTag(cell.getAttributes()));
            buf.append(getEndTag(cell.getAttributes()));
        }

        buf.append(getEndTag(row.getAttributes()));

        getDocument().setOuterHTML(row, buf.toString());
    }

    public void insertRowAbove(int pos) throws BadLocationException, IOException {
        Element row = getAncestor(pos, HTML.Tag.TR);

        StringBuffer buf = new StringBuffer();
        buf.append(getStartTag(row.getAttributes()));

        for (int i = 0; i < row.getElementCount(); i++) {
            Element cell = row.getElement(i);

            buf.append(getStartTag(cell.getAttributes()));
            buf.append(getEndTag(cell.getAttributes()));
        }

        buf.append(getEndTag(row.getAttributes()));
        buf.append(getOuterHTML(row));

        getDocument().setOuterHTML(row, buf.toString());
    }

    public void deleteRow(int pos) throws BadLocationException, IOException {
        Element row = getAncestor(pos, HTML.Tag.TR);
        Element table = row.getParentElement();

        StringBuffer buf = new StringBuffer();
        buf.append(getStartTag(table.getAttributes()));
        for (int i = 0; i < table.getElementCount(); i++) {
            Element e = table.getElement(i);
            if (e == row)
                continue;

            buf.append(getOuterHTML(e));
        }
        buf.append(getEndTag(table.getAttributes()));

        getDocument().setOuterHTML(table, buf.toString());
    }

    public void insertColumnRight(int pos) throws BadLocationException, IOException {
        Element cell = getAncestor(pos, HTML.Tag.TD);
        if (cell == null)
            cell = getAncestor(pos, HTML.Tag.TH);
        int cellIndex = getChildIndex(cell);
        Element table = getAncestor(pos, HTML.Tag.TABLE);

        for (int i = 0; i < table.getElementCount(); i++) {
            Element row = table.getElement(i);
            cell = row.getElement(cellIndex);

            SimpleAttributeSet atts = new SimpleAttributeSet(cell.getAttributes());
            if (atts.getAttribute(HTML.Attribute.WIDTH) != null) {
                String value = (String) atts.getAttribute(HTML.Attribute.WIDTH);
                if (value.endsWith("%")) {
                    int width = Integer.parseInt(value.substring(0, value.length() - 1));
                    width /= 2;
                    value = "" + width + "%";
                } else {
                    int width = Integer.parseInt(value);
                    width /= 2;
                    value = "" + width;
                }

                atts.removeAttribute(HTML.Attribute.WIDTH);
                atts.addAttribute(HTML.Attribute.WIDTH, value);
            }

            getDocument().setOuterHTML(cell, getStartTag(atts) + getInnerHTML(cell) + getEndTag(atts) + getStartTag(atts) + getEndTag(atts));
        }
    }

    public void insertColumnLeft(int pos) throws BadLocationException, IOException {
        Element cell = getAncestor(pos, HTML.Tag.TD);
        if (cell == null)
            cell = getAncestor(pos, HTML.Tag.TH);
        int cellIndex = getChildIndex(cell);
        Element table = getAncestor(pos, HTML.Tag.TABLE);

        for (int i = 0; i < table.getElementCount(); i++) {
            Element row = table.getElement(i);
            cell = row.getElement(cellIndex);

            SimpleAttributeSet atts = new SimpleAttributeSet(cell.getAttributes());
            if (atts.getAttribute(HTML.Attribute.WIDTH) != null) {
                String value = (String) atts.getAttribute(HTML.Attribute.WIDTH);
                if (value.endsWith("%")) {
                    int width = Integer.parseInt(value.substring(0, value.length() - 1));
                    width /= 2;
                    value = "" + width + "%";
                } else {
                    int width = Integer.parseInt(value);
                    width /= 2;
                    value = "" + width;
                }

                atts.removeAttribute(HTML.Attribute.WIDTH);
                atts.addAttribute(HTML.Attribute.WIDTH, value);
            }

            getDocument().setOuterHTML(cell, getStartTag(atts) + getEndTag(atts) + getStartTag(atts) + getInnerHTML(cell) + getEndTag(atts));
        }
    }

    public void deleteColumn(int pos) throws BadLocationException, IOException {
        Element cell = getAncestor(pos, HTML.Tag.TD);
        if (cell == null)
            cell = getAncestor(pos, HTML.Tag.TH);
        int cellIndex = getChildIndex(cell);
        Element table = getAncestor(pos, HTML.Tag.TABLE);

        for (int i = 0; i < table.getElementCount(); i++) {
            Element row = table.getElement(i);
            cell = row.getElement(cellIndex);

            remove(cell);
        }
    }
}
